package bancosys.tec.security.impl.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import jmine.tec.persist.annotation.Alias;
import jmine.tec.persist.annotation.Comment;
import jmine.tec.persist.annotation.Constraint;
import jmine.tec.persist.annotation.Index;
import jmine.tec.persist.annotation.UniqueConstraint;
import jmine.tec.persist.schema.api.IndexOrder;

import org.apache.commons.lang.builder.HashCodeBuilder;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.Filters;
import org.hibernate.annotations.ForeignKey;
import org.hibernate.validator.NotNull;

import bancosys.tec.component.Documentation;
import bancosys.tec.persist.authorization.annotation.DisplayName;
import bancosys.tec.persist.bussobj.AuthPersistableBusinessObject;
import bancosys.tec.persist.persister.annotation.NaturalKey;
import bancosys.tec.security.authorization.Permission;

/**
 * Classe base para as credencias.
 * 
 * @author Piercio
 */
@Entity
@Table(name = "CREDENTIAL")
@Alias("CREDEN")
@DiscriminatorColumn(name = "TYPE_CREDENTIAL", discriminatorType = DiscriminatorType.INTEGER)
@Comment(table = "CREDENTIAL", column = "TYPE_CREDENTIAL", value = "IDENTIFICA O TIPO DA CREDENCIAL.")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@SequenceGenerator(name = "SEQ_CREDEN", sequenceName = "SEQ_CREDEN")
@Filters({ @Filter(name = "authFilter", condition = "auth = :auth") })
@Constraint(suffix = "0")
@Documentation("ARMAZENA AS CREDENCIAIS QUE DAO AO USUARIO/GRUPO ACESSO AO SISTEMA. UMA CREDENCIAL PODE SER UMA URL, UM METODO DE UM BEAN (ACTION),"
        + "UMA TAREFA (TASK) OU UMA ENTIDADE UTILIZADA PARA CHINESE WALL, POR EXEMPLO, CARTEIRA.")
@UniqueConstraint(suffix = "JT_0", columns = { "MNE", "AUTH" })
@Index(suffix = "JT_1", columns = { "COD_CREDENTIAL", "TYPE_CREDENTIAL" })
public abstract class Credential extends AuthPersistableBusinessObject implements Serializable {

    // numero impar qquer
    private static final int HASH_RANDOM_1 = 25;

    // numero impar diferente do primeiro qquer
    private static final int HASH_RANDOM_2 = 49;

    /**
     * ID da credencial.
     */
    private Long id;

    /**
     * O sistema cliente dessa credencial.
     */
    private SecurityClient client;

    private String mnemonico;

    private MetaCredential metaCredential;

    /**
     * @return ID da credencial.
     */
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CREDEN")
    @Column(name = "COD_CREDENTIAL", nullable = false)
    @Documentation("CODIGO DA CREDENCIAL.")
    public Long getId() {
        return this.id;
    }

    /**
     * @return {@link String} com o mnemonico do credential
     */
    @NotNull
    @NaturalKey
    @Column(name = "MNE", length = 45, nullable = false)
    @Documentation("MNEMONICO DA CREDENCIAL")
    public String getMnemonico() {
        return this.mnemonico;
    }

    /**
     * @param mnemonico que serve como chave da credential
     */
    public void setMnemonico(String mnemonico) {
        this.mnemonico = mnemonico;
    }

    /**
     * Define o ID da credencial.
     * 
     * @param id ID da credencial.
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * Devolve o sistema cliente.
     * 
     * @return o sistema cliente.
     */
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "COD_SEC_CLIENT")
    @ForeignKey(name = "FK_SECCLI_CREDEN")
    @DisplayName(value = "Sistema", order = "20")
    @Documentation("IDENTIFICADOR DO SISTEMA CLIENTE DA SEGURANCA.")
    @Index(columns = "COD_SEC_CLIENT", suffix = "1", orders = IndexOrder.ASC)
    public SecurityClient getClient() {
        return this.client;
    }

    /**
     * Define o sistema cliente.
     * 
     * @param client o sistema cliente a ser definido.
     */
    public void setClient(SecurityClient client) {
        this.client = client;
    }

    /**
     * @param metaCredential the metaCredential to set
     */
    public void setMetaCredential(MetaCredential metaCredential) {
        this.metaCredential = metaCredential;
    }

    /**
     * @return the metaCredential
     */
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "COD_META_CREDENTIAL", nullable = true)
    @Documentation("CODIGO DA META CREDENCIAL")
    @Index(orders = IndexOrder.ASC, suffix = "0")
    @DisplayName(value = "Meta Credencial", order = "1")
    public MetaCredential getMetaCredential() {
        return this.metaCredential;
    }

    /**
     * Cria uma {@link Permission} a partir desta {@link Credential}
     * 
     * @return {@link Permission}
     */
    public abstract Permission createPermission();

    /**
     * Descrição da credencial a ser apresentada na autorização. Usualmente montada dinamicamente a partir dos campos específicos de cada
     * subclasse.
     * 
     * @return uma descrição desta credencial
     */
    @DisplayName(value = "Descrição", order = "2")
    @Transient
    public String getDynamicDescription() {
        return this.toString();
    }

    /**
     * Compara esse objeto com o passado como parâmetro. Utiliza nome da entidade como critério de comparação.
     * 
     * @param obj o outro objeto.
     * @return <code>true</code> se forem iguais, <code>false</code> caso contrário.
     */
    @Override
    public boolean equals(Object obj) {

        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof Credential)) {
            return false;
        }

        Credential other = (Credential) obj;

        if (this.mnemonico.equals(other.getMnemonico())) {
            return true;
        }

        return false;
    }

    /**
     * Devolve o hashCode.
     * 
     * @return o hashCode.
     */
    @Override
    public int hashCode() {
        HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(HASH_RANDOM_1, HASH_RANDOM_2);
        hashCodeBuilder.append(this.mnemonico);
        return hashCodeBuilder.toHashCode();
    }

}
